package com.Cfeng.XiaohuanChat.service.impl;

import com.Cfeng.XiaohuanChat.domain.Dtree;
import com.Cfeng.XiaohuanChat.domain.FileConstant;
import com.Cfeng.XiaohuanChat.entity.ChatUser;
import com.Cfeng.XiaohuanChat.entity.UserFile;
import com.Cfeng.XiaohuanChat.repository.ChatUserRepository;
import com.Cfeng.XiaohuanChat.repository.UserFileRepository;
import com.Cfeng.XiaohuanChat.service.UserMinIOFileService;
import com.indCfeng.minio.MinioService;
import com.mysql.cj.util.StringUtils;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MinioClient;
import io.minio.ObjectWriteResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.security.Principal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Cfeng
 * @date 2022/8/12
 * 用户文件上传到Minio，需要借助starter中的MinioService完成
 * 同时需要借助ChatUser的服务
 */

@Service
@RequiredArgsConstructor
public class UserMinioFileServiceImpl implements UserMinIOFileService {

    private final UserFileRepository userFileRepository;

    private final ChatUserRepository chatUserRepository;
    //借助自动配置的该服务类
    private final MinioService minioService;

    //获取url需要自动配置的client
    private final MinioClient minioClient;

    //执行多个步骤，需要加上事务
    @Override
    @Transactional(rollbackFor = Exception.class) //默认的传播行为为Required
    public String addFile(Principal principal, MultipartFile multipartFile,String dirIds) throws Exception {
        if(Objects.isNull(multipartFile)) {
            throw new RuntimeException("文件不能为空");
        }
        //首先查找当前登录用户
        String userName = principal.getName();
        ChatUser user = chatUserRepository.findByUserName(userName);
        //上传到服务器的文件名随机生成UUID
        String objectName = UUID.randomUUID().toString();
        //将文件上传到MinIO，文件流形式
        ObjectWriteResponse objectWriteResponse = minioService.uploadObject(objectName,multipartFile.getInputStream());
        //创建UserFile持久化
        UserFile userFile = new UserFile();
        //配置存储的属性
        String dirId = dirIds.substring(dirIds.lastIndexOf(FileConstant.DIR_SPLIT) + 1);
        if(FileConstant.DIR_SPLIT.equals(dirId) || StringUtils.isNullOrEmpty(dirId)) {
            //当前结点为最高级
            userFile.setParentId(FileConstant.ROOT_PARENT_ID);
        } else {
            //为当前文件的上一级设置
            Optional<UserFile> file = userFileRepository.findById(Long.parseLong(dirId));
            if(!file.isEmpty()) {
                throw new RuntimeException("出错了" + this.getClass().getName());
            } else {
                userFile.setParentId(file.get().getId());
            }
        }
        String suffix = multipartFile.getOriginalFilename().substring(multipartFile.getOriginalFilename().lastIndexOf(FileConstant.SUFFIX_SPLIT) + 1);
        //查询当前文件在存储中是否有同名的，如果同名就需要重命名为XXX(1)、XXX(2)
        int flag = 0;
        userFile.setFileName(this.recursionFindFileName(multipartFile.getOriginalFilename(),multipartFile.getOriginalFilename(),userFile.getParentId(),flag));
        userFile.setFileSize(multipartFile.getSize());
        userFile.setObjectName(objectWriteResponse.object());
        userFile.setUrl(String.format("%s/%s",principal.getName(),multipartFile.getOriginalFilename()));
        userFile.setIsDir(false);
        userFile.setType(suffix);
        if (suffix.equalsIgnoreCase("png") || suffix.equalsIgnoreCase("jpg") || suffix.equalsIgnoreCase("gif")) {
            userFile.setIsImg(Boolean.TRUE);
        } else {
            userFile.setIsImg(Boolean.FALSE);
        }
        userFile.setUploadTime(LocalDateTime.now());
        userFile.setUser(user);
        //同时user表需要记录
        System.out.println(userFile);
        //持久化并添加给user
        UserFile result = userFileRepository.save(userFile);
        if(Objects.isNull(result)) {
            return "文件" + multipartFile.getOriginalFilename() + "上传失败";
        }
        user.getFileList().add(result);
        return "上传成功";
    }

    /**
     * @param originName 文件原名
     * @param rname 修改之后的名称
     * @param parentId  父结点的名称【文件夹】
     * @param flag  标记值, 同名称文件的后缀 同名之后自动会进行加一
     * 查询文件是否存在，不是文件夹
     *  在当前文件夹下面查询rename后的文件 ---- 名称相同时会自动加上（X）
     *  该方法主要功能为存储文件时查询是否有该文件，如果有就重命名直到没有与该文件同名的
     */
    private String recursionFindFileName(String originName, String rname, Long parentId, int flag) {
        //查询当前文件夹下面所有的文件【使用rname查询】数量
        Integer count = userFileRepository.countByFileNameAndIsDirAndParentId(rname,Boolean.FALSE,parentId);
        if(count > 0) {
            flag ++;
            rname = originName + "(" + flag + ")";
            //查询到了有同名的文件那么就重命名为XX(1)或者XX(2),继续按照rname查询看是否还是同名
            return recursionFindFileName(originName,rname,parentId,flag);
        }
        //如果同名文件，那么就返回重命名后的文件名
        return flag > 0 ? originName + "(" +flag + ")": originName;
    }

    //查询当前目录下所有的文件
    @Override
    public List<UserFile> getFileList(UserFile userFile) {
        //从前台传入的数据对象直接提取dirIds查询其子文件并且排序
        String dirIds = userFile.getDirIds();
        //获得文件ID
        if(!StringUtils.isNullOrEmpty(dirIds)) {
            dirIds = dirIds.substring(dirIds.lastIndexOf(FileConstant.DIR_SPLIT) + 1);
            if(!StringUtils.isNullOrEmpty(dirIds)) {
                //查询子文件
                return userFileRepository.findAllByParentIdOrderByIsDirDescUploadTimeDesc(Long.parseLong(dirIds));
            } else {
                //为空，代表为最高级目录/,查询根节点子目录
                return userFileRepository.findAllByParentIdOrderByIsDirDescUploadTimeDesc(FileConstant.ROOT_PARENT_ID);
            }
        }
        //没有文件
        return null;
    }

    @Override
    public List<Dtree> getTreeList(UserFile userFile) {
        //查询和当前文件类型相同的文件列表
        List<UserFile> fileList = null;
        if(userFile.getIsDir() != null && userFile.getIsDir()) {
            //如果是文件夹，查询
            fileList = userFileRepository.findAllByIsDirOrderByIsDirDescUploadTimeDesc(userFile.getIsDir());
        }
        List<Dtree> dtreeList = new ArrayList<>();
        //文件列表中处理所有的文件夹
        fileList.forEach(item -> {
            Dtree dtree = new Dtree();
            //对象克隆
            BeanUtils.copyProperties(item,dtree);
            dtree.setTitle(item.getFileName());
            //设置图标
            if(dtree.getIsDir()) {
                //文件夹图标
                dtree.setIconClass(FileConstant.DTREE_ICON_1);
            } else {
                dtree.setIconClass(FileConstant.DTREE_ICON_2);
            }
            dtreeList.add(dtree);
        });
        return dtreeList.stream()
                        .filter(file -> file.getParentId() == -1) //根节点的子结点
                        .peek(file -> file.setChildren(this.getChildren(file,dtreeList))) //每个结点都查询子节点
                        .collect(Collectors.toList());
    }

    @Override
    public List<Dtree> getDirTreeList(UserFile userFile) {
        userFile.setIsDir(Boolean.TRUE);
        return this.getTreeList(userFile);
    }

    /**
     * 递归查询子节点
     * 第一个参数为待查询结点，第二个参数为整个树列表
     * 在整个树列表中查询属于当前结点的子结点，查询的每一个结点都会递归设置当前结点的children结点
     */
    private List<Dtree> getChildren(Dtree root, List<Dtree> treeList) {
        return treeList.stream()
                        .filter(file -> Objects.equals(file.getParentId(),root.getId())) //过滤出列表中所有的属于root的子节点
                        .peek(file -> file.setChildren(this.getChildren(file,treeList))) //当前结点继续查询子节点
                        .collect(Collectors.toList());
    }

    @Override
    public Optional<UserFile> getFile(String userName, String fileName) {
        ChatUser user = chatUserRepository.findByUserName(userName);
        UserFile result = userFileRepository.findByFileNameAndUser(fileName,user);
        return Optional.of(result);
    }

    /**
     * @param fileId
     * @return
     * @throws Exception
     * 删除文件ID,需要递归删除
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean deleteFile(long fileId) throws Exception {
        List<Long> idList = new ArrayList<>();

        List<String> keys = new ArrayList<>();
        //需要同时删除MinIO上面的文件
        Optional<UserFile> fileOptional = userFileRepository.findById(fileId);
        if(fileOptional.isPresent()) {
            UserFile file = fileOptional.get();
            //数组对象可以改变
            this.selectPermissionChildById(file.getId(),idList,keys);
            //当前对象id也需要删除
            idList.add(fileId);
            //删除MinIO的文件
            keys.add(file.getObjectName()); //当前文件也需要删除
            for(String key : keys) {
                minioService.removeObject(key);
            }
            //删除本地的,这里如果大数据量存在隐患，应该使用原生SQL
            userFileRepository.deleteAllById(idList);
            return true;
        }
        return false;
    }

    @Override
    public boolean deleteFileByUrl(String url) throws Exception {
        UserFile userFile = userFileRepository.findByUrl(url);
        Long id = userFile.getId();
        return this.deleteFile(id);
    }

    /**
     * @param pid
     * @param idList 子文件名称
     * @param keys  资源名称
     * 查询当前文件的子文件Id，删除时可以删除所有
     */
    private  void selectPermissionChildById(Long pid,List<Long> idList,List<String> keys) {
        List<UserFile> childList = userFileRepository.findAllByParentId(pid);
        //将childList里面菜单id取出来，封装到idList里面，方便删除时递归删除
        if(!CollectionUtils.isEmpty(childList)) {
            childList.forEach(child -> {
                idList.add(child.getId());
                //如果资源不是文件夹,把资源存储在MinIO封装到keys中
                if(!child.getIsDir()) {
                    keys.add(child.getObjectName());
                }
                //递归查询
                this.selectPermissionChildById(child.getId(),idList,keys);
            });
        }

    }

    /**
     * @param ids 前台传入的id,id字符串
     * @param parentId 当前的id的父级id
     * 拆分文件之后
     */
    @Override
    public boolean moveFile(String ids, Long parentId) {
        if(StringUtils.isNullOrEmpty(ids)) {
            throw new RuntimeException("请选择要移动的文件夹");
        }
        //拆分文件
        String[] idArray = ids.split(FileConstant.STRING_SPLIT);
        UserFile userFile = null;
        for(String id : idArray) {
           //查询出原来的
            userFile = userFileRepository.findById(Long.parseLong(id)).get();
            //修改父目录
            userFile.setParentId(parentId);
            //更新
            if(userFileRepository.save(userFile) == null) {
                throw new RuntimeException("移动失败");
            }
        }
        return true;
    }

    /**
     * @param userFile
     * 添加文件夹，与添加文件不同，不需要上传到MinIO，只需要持久化，设置IsDir为true
     * 也需要判断
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean addFolder(UserFile userFile) {
        String dirId = userFile.getDirIds().substring(userFile.getDirIds().lastIndexOf(FileConstant.DIR_SPLIT) + 1);
        //获得父文件ID之后，添加
        if(FileConstant.DIR_SPLIT.equals(dirId) || StringUtils.isNullOrEmpty(dirId)) {
            userFile.setParentId(FileConstant.ROOT_PARENT_ID);
        } else {
            Optional<UserFile> file = userFileRepository.findById(Long.parseLong(dirId));
            if(file.isEmpty()) throw new RuntimeException("当前文件不存在");
            userFile.setParentId(file.get().getId());
        }
        //设置file属性,展示类型为dir
        userFile.setType(FileConstant.DEFAULT_DIR_TYPE);
        userFile.setIsDir(Boolean.TRUE);
        userFile.setIsImg(Boolean.FALSE);
        userFile.setFileSize(0L);
        userFile.setUploadTime(LocalDateTime.now());
        //判断文件夹是否重名
        Integer count = userFileRepository.countByFileNameAndIsDirAndParentId(userFile.getFileName(),Boolean.TRUE,userFile.getParentId());
        if(count > 0) throw new RuntimeException("当前目录名称已经存在，修改后重试");
        return userFileRepository.save(userFile) == null;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean updateByName(UserFile userFile) {
        if(Objects.equals(userFile.getFileName(),userFile.getRename())) {
            throw new RuntimeException("当前名称与原始名称相同,请修改后重试");
        }
        UserFile file = userFileRepository.findById(userFile.getId()).get();
        //查询当前目录下同名的文件/夹数量
        Integer count =  userFileRepository.countByFileNameAndIsDirAndParentId(userFile.getRename(),file.getIsDir(),file.getParentId());
        if(count > 0) {
            throw  new RuntimeException("当前目录已存在该名称,修改后重试");
        }
        //修改原文件/夹的name为传入的新名称
        file.setFileName(userFile.getRename());
        return userFileRepository.save(file) == null;
    }

    /**
     * @param id
     * 获得当前的完整目录
     */
    @Override
    public Map<String, Object> getDirs(Long id) {
        Map<String,Object> map = new HashMap<>();
        //查询所有的父级结点
        List<UserFile> fileList = userFileRepository.selectParentList(id);
        //拼接目录
        StringBuilder dir = new StringBuilder(FileConstant.DIR_SPLIT);
        StringBuilder dirIds = new StringBuilder(FileConstant.DIR_SPLIT);
        if(!CollectionUtils.isEmpty(fileList)) {
            for(UserFile userFile : fileList) {
                //dir为路径，dirIds为存储的各级的id
                dir.append(userFile.getFileName()).append(FileConstant.DIR_SPLIT);
                dirIds.append(userFile.getId()).append(FileConstant.DIR_SPLIT);
            }
        }
        //项map中放置实际的资源路径和供后台使用的id路径
        map.put("dirs",dir.length() > 0 ? dir.deleteCharAt(dir.length() -1).toString() : "");
        map.put("dirIds",dirIds.length() > 0 ? dirIds.deleteCharAt(dirIds.length() - 1) : "");
        return map;
    }
}
